﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Diagnostics;
using System.Threading;

/// <summary>
/// Starts an an exe file / program
/// </summary>
public static class ProgramHandler
{
    /// <summary>
    /// To find out if the converter is running or not
    /// </summary>
    public static bool m_converterStarted = false;
    private static object m_converterLock = new object();
    //Need a separate thread so you can wait for the Process to finish
    private static Thread m_converterThread = null;
    //The process to run the converter exe
    private static Process m_converterProgram = null;
    //1 800 000 milliseconds equals 30 minutes,  1000 ms for 1 second,  60 000 for 1 minute
    private const int m_converterTimeout = 1800000;   

    /// <summary>
    /// Starts the converter and uses the input file to convert it to given output.
    /// </summary>
    /// <param name="a_input">The input filepath</param>
    /// <param name="a_output">The name of the file</param>
    /// <param name="a_inputExt">The extension of input file</param>
    /// <param name="a_session">The user http session variable</param>
    public static void TryToStartConverter(string a_input, string a_output, string a_inputExt, System.Web.SessionState.HttpSessionState a_session, ModelItem a_item)
    {
        //Check if thread and program doesn't exist
        if (!m_converterStarted)//if (m_converterThread == null && m_converterProgram == null)
        {
            //Then lock the object assigned as a lock
            lock (m_converterLock)
            {
                //Check again if someone else managed to initiate before the lock
                if(!m_converterStarted)//if (m_converterThread == null && m_converterProgram == null)
                {
                    StartConverter(a_input, a_output, a_inputExt, a_session, a_item);                   
                }
            }
        }        
    }

    /// <summary>
    /// To cancel the converter. It kills the process and that automatically should kill the thread
    /// </summary>
    public static void CancelConverter(System.Web.SessionState.HttpSessionState a_session)
    {
        //A try catch just to be sure incase of odd exception
        try
        {
            CancelProgram(m_converterProgram);
        }
        catch
        {
            
        }        
    }

    /// <summary>
    /// Starts the converter and the new thread where it will be running on
    /// </summary>    
    private static void StartConverter(string a_input, string a_output, string a_extension, System.Web.SessionState.HttpSessionState a_session, ModelItem a_item)
    {
        //Anonymous inline function to repeat Abort Thread behavior
        Action AbortThread = () =>
        {
            m_converterThread = null;
            //Brute forces the Program thread to cancel
            CancelProgram(m_converterProgram);
            m_converterProgram = null;
            a_session[Strings.Session_Converter_Msg] = Strings.converter_threadcrash;
        };

        try
        {
            m_converterStarted = true;
            //Get the process
            string f_arguments = "-i " + a_input;            
            f_arguments += " -o " + a_output;
            f_arguments += " -c " + a_extension;
            //f_arguments += " -wd " + Paths.admin_cpp_folder;
            f_arguments += " -wd " + "/../../Models/ -rel";

            m_converterProgram = StartProgram(Paths.admin_cpp_converterFile, f_arguments);
            //If the process didn't start
            if (m_converterProgram == null)
            {
                AbortThread();
                return;
            }
            //Create the thread
            m_converterThread = new Thread(() => ConverterThread(a_input ,a_session, a_item) );  /*() => StartProgram("the converter file.exe", a_input + " " + a_output + " " + a_extension) );*/
            a_session[Strings.Session_Converter_Msg] = Strings.converter_started;
            //Set bool that converter started to true
            a_session[Strings.Session_Converter_Started] = true;
            //Start the thread
            m_converterThread.Start();                        
        }
        catch
        {
            /*m_converterThread = null;
            CancelProgram(m_converterProgram);
            m_converterProgram = null;
            a_session[Strings.Session_Converter_Msg] = Strings.converter_threadcrash;*/
            AbortThread();
        }        
    }

    /// <summary>
    /// The thread where the converter is running
    /// </summary>
    private static void ConverterThread(string a_inputFile, System.Web.SessionState.HttpSessionState a_session, ModelItem a_item)
    {
        bool f_succeeded = false;

        try
        {
            //Pauses this thread as it will wait for program to run thanks to WaitForExit()
            //Finds out if RunProgram succeded going from start -> finish without crashing
            f_succeeded = RunProgram(m_converterProgram, m_converterTimeout);
            //Program finished or was canceled by a third party, the thread is resumed!            

            //If the program succeded then add the modelitem finally to the modelslist file
            if (f_succeeded)
            {
                //TODO: REMOVE COMMENTED CAUSE, TESTING!
                //ModelItemHandler.AddModelItem(a_item);
            }

            //Delete the temp file
            System.IO.File.Delete(a_inputFile);            
        }
        catch (Exception e)
        {
            
        }
        //When everything is finished
        a_session[Strings.Session_Converter_Msg] = Strings.converter_finished;
        m_converterThread = null;
        m_converterProgram = null;
        m_converterStarted = false;
        //The bool to say if converter is running or not
        a_session[Strings.Session_Converter_Started] = false; 
    }

    /// <summary>
    /// Creates a process based 
    /// </summary>   
    private static Process StartProgram(string a_filename, string a_args)
    {       
        try
        {
            ProcessStartInfo f_startInfo = new ProcessStartInfo(a_filename, a_args);
            //process info required
            f_startInfo.CreateNoWindow = false;
            f_startInfo.UseShellExecute = false;
            //f_startInfo.WindowStyle = ProcessWindowStyle.Hidden;
            //f_startInfo.UserName = Secret.WINDOWS_USERDETAILS.Username;
            //f_startInfo.Password = Secret.WINDOWS_USERDETAILS.GetPassword();
            //f_startInfo.Domain = "";
            //f_startInfo.Verb = "runas";
            //f_startInfo.LoadUserProfile = true;           

            Process f_process = Process.Start(f_startInfo);
            return f_process;
        }
        catch
        {

        }

        return null;
    }
    
    /// <summary>
    /// Tells the thread to wait for the program
    /// </summary>    
    private static bool RunProgram(Process a_program, int a_timeout = -1)
    {
        //I think the program is already running by now but the a_program.WaitForExit makes sure it pauses the thread.
        try
        {
            if (a_timeout == -1)
            {
                a_program.WaitForExit();
            }
            else
            {
                a_program.WaitForExit(a_timeout);
            }
            //Try to cancel program for instance if it took longer than a_timeout
            return CancelProgram(a_program);
            
        }
        catch
        {

        }

        return false;
    }

    /// <summary>
    /// Kills a process,  also returns true if program ended naturally
    /// </summary> 
    private static bool CancelProgram(Process a_program)
    {
        bool f_success = false;

        if (a_program != null)
        {
            if (!a_program.HasExited)
            {
                if (a_program.Responding)
                {
                    a_program.Close();
                }
                else
                {
                    a_program.Kill();
                }
            }
            else
            {
                //If program exited succesfully
                if (a_program.ExitCode == 0)
                {
                    f_success = true;
                }                
            }
            
        }

        return f_success;
    }    
}